如何生成更小的 C 和 C++ 二进制文件
这篇博文介绍了几种使用 GCC(或 Clang)使 C 或 C++ 编译生成的二进制文件更小的技术。请注意,几乎所有技术都是折衷的,即较小的二进制文件可能更慢且更难调试。因此,在了解权衡之前,不要盲目地使用这些技术。
推荐的 GCC(和 Clang)标志:
- 使用-s从二进制文件中去除调试信息(不要使用-g)。
- 使用-Os优化输出文件大小。(这会使代码运行速度比-O2或-O3慢。
- 使用-m32编译 32 位二进制文件。32 位二进制文件比 64 位二进制文件小,因为指针更短。
- 在 C++ 中,如果您的代码不使用异常,请使用-fno-exceptions 。
- 在 C++ 中,如果您的代码不使用 RTTI(运行时类型识别)或dynamic_cast ,请使用-fno-rtti。
- 在 C++ 中,使用-fvtable-gc让链接器知道并删除未使用的虚方法表。
- 使用-fno-stack-protector。
- 使用-fomit-frame-pointer(这可能会使代码在 amd64 上变大)。
- 使用-ffunction-sections -fdata-sections -Wl,–gc-sections。如果没有这个,来自每个需要的.o文件的所有代码都将被包括在内。这样只会包含所需的代码。
- 对于 i386,使用-mpreferred-stack-boundary=2。
- 对于 i386,使用-falign-functions=1 -falign-jumps=1 -falign-loops=1。
- 在 C 中,使用-fno-unwind-tables -fno-asynchronous-unwind-tables。其中,-fno-asynchronous-unwind-tables产生了更大的差异(可能是几千字节)。
- 使用-fno-math-errno,并且在调用数学函数后不检查错误号。
- 尝试-fno-unroll-loops,有时它会使文件变小。
- 使用-fmerge-all-constants。
- 使用-fno-ident,这将阻止生成.ident汇编程序指令,该指令将编译器的标识添加到二进制文件中。
- 使用-mfpmath=387 -mfancy-math-387缩短浮点计算时间。
- 如果不需要双精度,但浮点精度就足够了,请使用-fshort-double -fsingle-precision-constant。
- 如果您不需要符合 IEEE 标准的浮点计算,请使用-ffast-math。
- 使用-Wl,-z,norelro进行链接,相当于ld -z norelro。
- 使用-Wl,–hash-style=gnu进行链接,相当于ld –hash-style=gnu。您也可以尝试=sysv而不是=gnu,有时它会小几个字节。这里的目标是避免=both,这是某些系统的默认设置。
- 使用-Wl,–build-id=none进行链接,相当于ld –build-id=none。
- 从diet.c的diet.c中的Os列表中获取更多标志,用于大约 15 种架构。
- 不要使用这些标志:-pie、-fpie、-fPIE、-fpic、-fPIC。其中一些在共享库中很有用,因此仅在编译共享库时启用它们。
其他减少二进制大小的方法:
http://www.muppetlabs.com/~breadbox/software/elfkickers.html
- 运行strip -S –strip-unneeded –remove-section=.note.gnu.gold-version –remove-section=.comment –remove-section=.note –remove-section=.note.gnu。生成的二进制文件上的build-id –remove-section=.note.ABI-tag以去除更多不需要的部分。这用更积极的剥离替换了gcc -s标志。
- 如果您使用的是 uClibc 或diet libc ,则在生成的二进制文件上另外运行strip –remove-section=.jcr –remove-section=.got.plt 。
- 如果您使用带有-fno-exceptions的 C 或 C++ 的 uClibc 或diet libc ,则在生成的二进制文件上另外运行strip –remove-section=.eh_frame –remove-section=.eh_frame_ptr 。
- 在上面运行strip …之后,还要对二进制文件运行sstrip 。从ELF Kickers下载 sstrip ,然后自己编译。或者从这里获取 3.0a 二进制文件。
- 在 C++ 中,避免使用 STL。请改用 C 库函数。
- 在 C++ 中,使用尽可能少的模板类型(即带有vector和vector 的代码的长度是仅带有vector 的代码的两倍)。
- 在 C++ 中,让每个非 POD(普通旧数据)类都有显式构造函数、析构函数、复制构造函数和赋值运算符,并在类外部的 .c 文件中实现它们。
- 在 C++ 中,将构造函数、析构函数和方法体移到类外部的.c文件中。
- 在 C++ 中,使用较少的虚拟方法。
- 使用UPX压缩二进制文件。对于小型二进制文件,使用upx –brute或upx –ultra-brute。对于大型二进制文件,请使用upx –lzma。如果您的代码中有大型初始化数组,请确保将它们声明为const,否则 UPX 不会压缩它们。
- 使用 UPX 压缩使用的库。
- 如果您使用静态链接(例如gcc -static),请改用uClibc(最方便的方式:pts-xstatic或diet libc(最方便的方式:包含的diet工具)或musl(最方便的方式:包含的musl-gcc工具) glibc(GNU C 库)。
- 使每个函数静态化,创建一个包含所有其他.c文件的.c文件,并使用gcc -W -Wall编译它。删除编译器认为未使用的所有代码。上次这为我每个函数节省了大约 9.2 个字节。
- 不要在函数上使用__attribute__((regparm(3))),它会使代码变大。
- 如果您有多个二进制文件和共享库,请考虑将二进制文件统一为一个(使用符号链接并在main中区分argv[0]),并将库代码移动到二进制文件中。这很有用,因为共享库使用更大的位置无关代码 (PIC)。
- 如果可行,请将 C++ 代码重写为 C。一旦是 C,使用gcc或g++编译都没有关系。
- 如果您的二进制文件已经小于 10 KB,请考虑在汇编中重写它,并手动生成 ELF 标头,请参阅微型 ELF页面以获取灵感。
- 如果您的二进制文件已经小于 10 KB,并且您不使用任何 libc 函数,请使用链接描述文件生成微型 ELF 标头。请参阅包含链接描述文件的 tarball。
- 删除gcc传递给ld 的–hash -style=…标志。为此,将-Bmydir标志传递给gcc,并创建可执行文件mydir/ld,它会删除这些标志并调用真正的ld。
- 也可以在这里查看更多标志和方法。